#include "applib/lib_syscall.h"
#include <stdio.h>
#include "main.h"
#include <stdlib.h>
#include "string.h"
#include <getopt.h>     // 非标准c库函数，而是Newlib库
#include <sys/file.h>
#include "fs/file.h"
#include "dev/tty.h"


static cli_t cli;
static const char * promot = "sh >>";

// 函数声明
static int do_help(int argc, char ** argv);
static int do_clear(int argc, char ** argv);
static int do_echo(int argc, char ** argv);

static const cli_cmd_t * find_builtin(char * name);
static int run_builtin(const cli_cmd_t * cmd, int argc, char ** argv);

// 函数定义
static int do_help(int argc, char ** argv)
{
    const cli_cmd_t * cmd = cli.cmd_start;
    while(cmd < cli.cmd_end)
    {
        printf("%s -- %s\n", cmd->name, cmd->usage);
        cmd++;
    }
    return 0;
}

static int do_clear(int argc, char ** argv)
{
    // 使用CSI转义字符串清空屏幕
    printf("%s", ESC_CLEAR_SCREEN);
    printf("%s", ESC_MOVE_CURSOR(0, 0));
    return 0;
}

static int do_echo(int argc, char ** argv)
{
    if(argc == 1)
    {
        char msg_buff[128];
        fgets(msg_buff, sizeof(msg_buff), stdin);
        msg_buff[sizeof(msg_buff) - 1] = '\0';
        puts(msg_buff);
        return 0;
    }

    // 这里只是简单的判断，第二个参数是否以-x开头
    if(argc == 2 && argv[1][0] != '-')
    {
        puts(argv[1]);
        return 0;
    }


    int count = 1;
    int ch;                         // n后有: 代表-n后面有参数
                                    // h后没: 代表-h后面没参数
    while((ch = getopt(argc, argv, "n:h")) != -1)
    {
        if(ch == 'h')
        {
            puts("echo [-n count] messgae. echo something for count times.\n"
                    "if not have count, count = 1");
            optind = 1;
            return 0;
        }
        else if(ch == 'n')
        {
            count = atoi(optarg);
        }
        else if(ch == '?')
        {
            if(optarg)
            {
                fprintf(stderr, ESC_COLOR_ERROR"Unknown option: -%d\n"
                            ESC_COLOR_DEFAULT, optarg);
            }
            optind = 1;
            return -1;
        }
    }
    if(optind > argc - 1)
    {
        fprintf(stderr, ESC_COLOR_ERROR"Message is empty\n"ESC_COLOR_DEFAULT);
        optind = 1;
        return -1;
    }

    char * msg = argv[optind];
    for(int i=0;i<count;i++)
    {
        puts(msg);
    }
    optind = 1;
    return 0;
}

static int do_quit(int argc, char ** argv)
{
    exit(0);
    return 0;
}

static int do_ls(int argc, char ** argv)
{
    DIR * p_dir = opendir("temp");
    if(!p_dir)
    {
        printf("open dir failed.\n");
        return -1;
    }

    struct dirent * entry;
    while(entry = readdir(p_dir))
    {
        printf("%c %s %d\n",
            (entry->type == FILE_DIR) ? 'd' : 'f',
            entry->name,
            entry->size
        );
    }
    closedir(p_dir);
    return 0;
}

static int do_less(int argc, char ** argv)
{
    int line_mode = 0;
    int count = 1;
    int ch;                         
    while((ch = getopt(argc, argv, "lh")) != -1)
    {
        if(ch == 'h')
        {
            puts("less file contact.");
            optind = 1;
            return 0;
        }
        else if(ch == 'l')
        {
            line_mode = 1;
        }
        else if(ch == '?')
        {
            if(optarg)
            {
                fprintf(stderr, ESC_COLOR_ERROR"Unknown option: -%d\n"
                            ESC_COLOR_DEFAULT, optarg);
            }
            optind = 1;
            return -1;
        }
    }
    if(optind > argc - 1)
    {
        fprintf(stderr, ESC_COLOR_ERROR"No file\n"ESC_COLOR_DEFAULT);
        optind = 1;
        return -1;
    }

    FILE * file = fopen(argv[optind], "r");
    if(!file)
    {
        fprintf(stderr, ESC_COLOR_ERROR"open file failed.\n"
        "file = %d\n"ESC_COLOR_DEFAULT, argv[optind]);
        optind = 1;
        return -1;
    } 

    char * buff = (char *)malloc(255);
    if(line_mode == 0)
    {
        while (fgets(buff, 255, file) != NULL)
        {
            fputs(buff, stdout);
        }
    }
    else
    {
        setvbuf(stdin, NULL, _IONBF, 0);        // 关闭\n缓存
        ioctl(0, TTY_CMD_ECHO, TTY_CMD_ECHO_CLOSE, 0);      // 关闭回显
        while(1)
        {
            char * b = fgets(buff, 255, file);
            if(!b) break;

            fputs(buff, stdout);

            char c;
            while( (c = fgetc(stdin)) != 'n')
            {
                if(c == 'q') goto less_quit;
            }
        }
    }
less_quit:
    ioctl(0, TTY_CMD_ECHO, TTY_CMD_ECHO_OPEN, 0);      // 开启回显
    setvbuf(stdin, NULL, _IOLBF, BUFSIZ);
    free(buff);

    fclose(file);
    optind = 1;
    return 0;
}

static int do_cp(int argc, char ** argv)
{
    if(argc < 3)
    {
        fprintf(stderr, "no [from] or no [to]");
        return -1;
    }
    FILE * from, * to;
    from = fopen(argv[1], "rb");
    to = fopen(argv[2], "wb");
    if(!from || !to)
    {
        fprintf(stderr, "open file failed");
        goto cp_failed;
    }

    char * buff = (char *)malloc(255);
    int size;
    while( (size = fread(buff, 1, 255, from)) > 0)
    {
        fwrite(buff, 1, size, to);
    }
    free(buff);

cp_failed:
    if(from)    fclose(from);
    if(to)    fclose(to);
    return 0;
}

static int do_touch(int argc, char ** argv)
{
    if(argc != 2)
    {
        fprintf(stderr, "args number error. must be 2, but get %d.\n",argc);
        return -1;
    }
    FILE * file;
    file = fopen(argv[1], "wb");
    if(!file)
    {
        fprintf(stderr, "create file failed");
        return -1;
    }

    return 0;
}

static int do_rm(int argc, char ** argv)
{
    if(argc != 2)
    {
        fprintf(stderr, "args number error. must be 2, but get %d.\n",argc);
        return -1;
    }

    int err = unlink(argv[1]);
    if(err < 0)
    {
        fprintf(stderr, "rm file failed. %s\n",argv[1]);
        return -1;
    }

    return 0;
}

static const cli_cmd_t cmd_list[] = {
    {
        .name = "help",
        .usage = "list all support command.",
        .do_func = do_help,
    },
    {
        .name = "clear",
        .usage = "clear screen",
        .do_func = do_clear,
    },
    {
        .name = "echo",
        .usage = "echo [-n count] messgae. echo something for count times."
                    "if not have count, count = 1",
        .do_func = do_echo,
    },
    {
        .name = "ls",
        .usage = "list director",
        .do_func = do_ls,
    },
    {
        .name = "cp",
        .usage = "copy file",
        .do_func = do_cp,
    },
    {
        .name = "touch",
        .usage = "creat file",
        .do_func = do_touch,
    },
    {
        .name = "rm",
        .usage = "remove file",
        .do_func = do_rm,
    },
    {
        .name = "less",
        .usage = "less [-l] file. show file contact",
        .do_func = do_less,
    },
    {   
        .name = "quit",
        .usage = "quit from shell.",
        .do_func = do_quit,
    }
};


static void cli_init(cli_t * cli, const char * promot, const cli_cmd_t * cmd_list, int size)
{
    cli->cmd_start = cmd_list;
    cli->cmd_end = cmd_list + size;
    cli->promot = promot;
    memset(cli->curr_input, 0, CLI_INPUT_SIZE);
}

static void show_promot()
{
    printf("%s", cli.promot);
    // 清空缓冲区，这样可以不需要\n也能输出
    fflush(stdout);
}

static const cli_cmd_t * find_builtin(char * name)
{
    for (const cli_cmd_t * cmd = cli.cmd_start; cmd < cli.cmd_end; cmd++) {
        if (strcmp(cmd->name, name) != 0) {
            continue;
        }
        return cmd;
    }
    return (const cli_cmd_t *)0;
}

static int run_builtin(const cli_cmd_t * cmd, int argc, char ** argv)
{
    int ret = cmd->do_func(argc, argv);
    if(ret < 0)
    {
        fprintf(stderr, ESC_COLOR_ERROR"error: %d\n"ESC_COLOR_DEFAULT, ret);
    }
}

static const char * find_exec_path(const char * name)
{
    static char pre_name[255];  // 实现不加扩展名也能运行磁盘程序
    int fd = open(name, 0);
    if(fd < 0)
    {
        sprintf(pre_name, "%s.elf", name);
        fd = open(pre_name, 0);
        if(fd < 0) return 0;
        close(fd);
        return pre_name;
    }
    
    close(fd);
    return name;
    
    

}

static void run_exec_file(const char * path, int argc, char ** argv)
{
    int pid = fork();
    if(pid < 0)
    {
        fprintf(stderr, 
                ESC_COLOR_ERROR"fork failed. path = %s\n"ESC_COLOR_DEFAULT, 
                path);
    }
    else if(pid == 0)
    {
        int err = execve(path, argv, 0);
        if(err < 0)
        {
            fprintf(stderr, "exec failed: %s", path);
        }
        exit(-1);
    }
    else
    {
        // 父进程(shell进程)等待新进程运行完，重新执行
        int status;
        int pid = wait(&status);
        fprintf(stderr, "cmd %s result : %d. pid = %d\n", path, status, pid);
    }
}

int main(int argc, char ** argv)
{    
    // 打开0号tty设备
    open(argv[0], O_RDWR);
    dup(0);
    dup(0);

    cli_init(&cli, promot, cmd_list, sizeof(cmd_list) / sizeof(cmd_list[0]));
    for(;;)
    {
        show_promot();
        char * cmd_input = fgets(cli.curr_input, CLI_INPUT_SIZE, stdin);
        if(!cmd_input)
        { 
            continue;
        }

        char * cr = strchr(cli.curr_input, '\n');
        if(cr)
        {
            *cr = '\0';
        }

        cr = strchr(cli.curr_input, '\r');
        if(cr)
        {
            *cr = '\0';
        }

        int cmd_argc = 0;
        char * cmd_argv[CLI_PARAM_COUNT_MAX];
        memset(cmd_argv, 0, sizeof(cmd_argv));
        // 分割字符串 类似于str.split()
        char * token = strtok(cli.curr_input, " ");
        while (token)
        {
            if(cmd_argc >= CLI_PARAM_COUNT_MAX)
            {
                break;
            }
            cmd_argv[cmd_argc++] = token;
            token = strtok(NULL, " ");
        }

        if(cmd_argc == 0)
        {
            continue;
        }        

        const cli_cmd_t * cmd = find_builtin(cmd_argv[0]);
        if(cmd) // 系统内置命令
        {
            run_builtin(cmd, cmd_argc, cmd_argv);
            continue;
        }

        const char * path = find_exec_path(cmd_argv[0]);
        if(path) run_exec_file(path, cmd_argc, cmd_argv);
        else fprintf(stderr, ESC_COLOR_ERROR"Unknown command: %s\n"ESC_COLOR_DEFAULT, cli.curr_input);
    }

}